//============================================================================
// Copyright (C) 2009-2013 Brett R. Jones 
// All Rights Reserved
//
// You may redistribute and/or modify for non commercial and commercial uses 
// provided this copyright notice remains in place and is not modified
//
// This code is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// brettjones1900@gmail.com
// http://www.p2panarchy.com
//============================================================================

#include <memory.h>
#include "RcSysSktMgrLib.h"
//#include "RcEngine/RcEngine.h"

//============================================================================
//=== globals ===//
//============================================================================
//============================================================================
//! handle socket callbacks
void RcSysSktMgrDefaultSktCallbackHandler( VxSktBase *  poSkt )
{
	log_msg( LOG_ERROR, "RcSysSktMgr::SktCallbackHandler is not set\n");
}

//---------------------------------------------------------------------------
U32 RcSysSktMgrTxEventsThreadFunc(  void * pvContext )
{
	VxThread * poThread = (VxThread *)pvContext;
	poThread->SetIsThreadRunning( true );
	RcSysSktMgr * poSktMgr = (RcSysSktMgr *)poThread->GetUserParam();
	while( false == poThread->isAborted() )
	{
		poSktMgr->ProcessTxEventQue();
	}
	//! Thread calls this just before exit
	poThread->ThreadAboutToExit();
	return 0;
}

//============================================================================
//=== constructor ===//
RcSysSktMgr::RcSysSktMgr()
    : m_u32UdpPort(0)
	, m_bEnableBroadcast(true)
    , m_bIsPhoneNetwork(true)  // if true we are on phone provider network
    , m_bIsOnline(true)
    , m_u32BroadcastTimeInterval(6) // time in seconds between broadcasts
    , m_bProcessEventsDirectly(true)
{
	// set our own callback
	this->SetRecieveCallback( RcSysSktMgrDefaultSktCallbackHandler );

	// set accept socket manager callback
	m_oInSktMgr.m_poParentMgr = this;
	m_oInSktMgr.SetRecieveCallback( RcSysSktMgrDefaultSktCallbackHandler );

	// set broadcast callback
    m_oSktBroadcast.m_poSktMgr = this;
    m_oSktBroadcast.SetRecieveCallback( RcSysSktMgrDefaultSktCallbackHandler );

    RcSysSktBroadcast * poSktBroadcast = new RcSysSktBroadcast();
    poSktBroadcast = poSktBroadcast;
    poSktBroadcast = NULL;
}

//============================================================================
void RcSysSktMgr::sysSktMgrStartup( VxInetAddress& oLocalIp, U16 u16UdpPort, bool bUdpBroadcastEnable )
{
	m_oLocalIpAddress = oLocalIp;
	m_u32UdpPort = u16UdpPort;
	m_bEnableBroadcast = bUdpBroadcastEnable;
	goOnline();
}

//============================================================================
//! Shutdown
void RcSysSktMgr::sysSktMgrShutdown( void )
{
	goOffline();
	RcSktConnectMgr::sktMgrShutdown();
	m_oInSktMgr.sktMgrShutdown();
}

//============================================================================
void RcSysSktMgr::goOnline( void )
{
	sktMgrSetLocalIp( m_oLocalIpAddress );
	m_oInSktMgr.sktMgrSetLocalIp( m_oLocalIpAddress );

	// start listening
	if( 0 == m_oInSktMgr.startListening( m_oPktAnn.getOnlinePort() ) )
	{
		if( m_bEnableBroadcast )
		{
			// start broadcasting
			log_msg( LOG_INFO, "RcSysSktMgr::goOnline starting broadcast port %d\n", m_u32UdpPort );
            m_oSktBroadcast.StartBroadcast( m_oLclIp, &m_oPktAnn, m_u32UdpPort, UDP_ANNOUNCE_BROADCAST_INTERVAL );
		}

		m_oTxEventThread.StartThread((VX_THREAD_FUNCTION_T)RcSysSktMgrTxEventsThreadFunc, this, "RcSysSktMgrTxEvents" );
	}
	else
	{
		log_msg( LOG_INFO, "RcSysSktMgr::goOnline failed listening on port %d\n", m_oPktAnn.getOnlinePort() );
	}
}

//============================================================================
void RcSysSktMgr::goOffline( void )
{
	m_oTxEventThread.Abort( true );
	m_oTxSemaphore.Signal();
	m_oTxEventThread.KillThread();
	// stop broadcasting
    m_oSktBroadcast.StopBroadcast();
	m_oInSktMgr.stopListening();
	m_oInSktMgr.ShutdownAllSkts();
	RcSktConnectMgr::ShutdownAllSkts();
}

//============================================================================
//! called after m_oPktAnn has changed
void RcSysSktMgr::onPktAnnUpdated( void )
{
    m_oSktBroadcast.onPktAnnUpdated( m_oPktAnn );
}

//============================================================================
//! this is called if incoming http connection
RCODE RcSysSktMgr::HandleHttpConnection	( RcSktBase * poSktBase )
{
	log_msg( LOG_INFO, "RcSysSktMgr::HandleHttpConnection not set\n");
	return poSktBase->CloseSkt();
}

//============================================================================
//! set announce packet with changes 
RCODE RcSysSktMgr::PktAnnounceChanged( RcSysPktAnnounce * poPktAnn )
{
    m_oSktBroadcast.StopBroadcast();

	m_oSktMgrMutex.Lock( __FILE__, __LINE__ );
	memcpy( &m_oPktAnn, poPktAnn, poPktAnn->m_u16PktLen );

	memcpy( &m_oPktPing, poPktAnn, poPktAnn->m_u16PktLen );
	m_oPktPing.m_u16PktType = RC_SYS_PKT_TYPE_PING;

	memcpy( &m_oPktPong, poPktAnn, poPktAnn->m_u16PktLen );
	m_oPktPing.m_u16PktType = RC_SYS_PKT_TYPE_PONG;

	m_oSktMgrMutex.Unlock( __FILE__, __LINE__ );
    m_oSktBroadcast.StartBroadcast( m_oLclIp, &m_oPktAnn );
	return 0;
}

//============================================================================
//! encrypt and send my PktAnnounce
RCODE RcSysSktMgr::SendMyPktAnnounce(	VxNetIdentBase *	poIdent,
										RcSktBase *			poSkt, 
										EFriendState		eMyFriendshipToHim, 
										EFriendState		eHisFriendshipToMe,
										bool				bRequestAnnReply,
										bool				requestReverseConnection,
										bool				requestSTUN )
{
	// make copy of pkt ann 
	RcSysPktAnnounce oPktAnn;
	memcpy( &oPktAnn, &m_oPktAnn, sizeof(RcSysPktAnnounce) );
	oPktAnn.setIsPktAnnReplyRequested( bRequestAnnReply );
	oPktAnn.setIsPktAnnRevConnectRequested( requestReverseConnection );
	oPktAnn.setIsPktAnnStunRequested( requestSTUN );
	// reverse permissions
	oPktAnn.setMyFriendshipToHim(eMyFriendshipToHim);
	oPktAnn.setHisFriendshipToMe(eHisFriendshipToMe);
	//log_msg( LOG_DEBUG, "SendMyPktAnnounce; to %s %s Hi 0x%llx, Lo 0x%llx skt %d request reply %d requires proxy %d\n", 
	//			g_oRcEngine.knownContactNameFromId( poIdent ),
	//			poIdent->getOnlineName(),
	//			poIdent->getMyOnlineIdHiPart(),
	//			poIdent->getMyOnlineIdLoPart(),
	//			poSkt->m_iSktId,
	//			bRequestAnnReply,
	//			oPktAnn.requiresRelay() );
	//send announcement
	return txSystemPkt( poIdent, poSkt, &oPktAnn ) ? 0 : 1;
}

//============================================================================
//! encrypt and send my PktAnnounce to someone of whom we have no recored except from web announce
bool RcSysSktMgr::SendMyPktAnnounce(	OnlineId&			oOnlineId, 
										RcSktBase *			poSkt, 
										EFriendState		eMyFriendshipToHim, 
										EFriendState		eHisFriendshipToMe,
										bool				bRequestAnnReply,
										bool				requestReverseConnection,
										bool				requestSTUN )
{
	RcSysPktAnnounce oPktAnn;
	memcpy( &oPktAnn, &m_oPktAnn, sizeof(RcSysPktAnnounce) );
	oPktAnn.setIsPktAnnReplyRequested( bRequestAnnReply );
	oPktAnn.setIsPktAnnRevConnectRequested( requestReverseConnection );
	oPktAnn.setIsPktAnnStunRequested( requestSTUN );

	// reverse permissions
	oPktAnn.setMyFriendshipToHim( eHisFriendshipToMe );
	oPktAnn.setHisFriendshipToMe( eMyFriendshipToHim );
	oPktAnn.m_oDestOnlineId = oOnlineId;

	//log_msg( LOG_DEBUG, "SendMyPktAnnounce; to %s Hi 0x%llx, Lo 0x%llx skt %d request reply %d\n", 
	//	g_oRcEngine.knownContactNameFromId( oOnlineId ),
	//	oOnlineId.getOnlineIdHiPart(),
	//	oOnlineId.getOnlineIdLoPart(),
	//	poSkt->m_iSktId,
	//	bRequestAnnReply );

	return poSkt->TxEncrypted(	(const char *)&oPktAnn, oPktAnn.m_u16PktLen ) ? false : true;	
}


//============================================================================
bool RcSysSktMgr::txSystemPkt(	VxNetIdentBase *	poIdent, 
								RcSktBase *			poSkt, 
								VxPktHdr *			poPkt, 
								bool				bDisconnectAfterSend )
{
	*((OnlineId*)poPkt) = poIdent->getMyOnlineId();
	return poSkt->TxEncrypted(	(const char *)poPkt,
								poPkt->m_u16PktLen )?0:1;	
}

//============================================================================
bool RcSysSktMgr::txPluginPkt( 	EPluginType			ePluginType, 
								VxNetIdentBase *	poIdent, 
								RcSktBase *			poSkt, 
								VxPktHdr *			poPkt, 
								bool				bDisconnectAfterSend )
{
	*((OnlineId *)poPkt) = poIdent->getMyOnlineId();
	return poSkt->TxEncrypted(	(const char *)poPkt,
								poPkt->m_u16PktLen ) ? 0 : 1;	
}

//============================================================================
//! attempt connect to remote user
RCODE RcSysSktMgr::DirectConnectTo(	VxNetIdentBase *		poIdent,		// network identity
									RcSktBase **			ppoRetSkt,		// return pointer to socket if not null
									int						iConnectTimeout,// how long to attempt connect
									void *					pvUserData,		// pointer to user additional user defined data
									bool					bUseUdpIp )
{
	RCODE rc = -1;
	* ppoRetSkt = NULL;

	std::string strIpAddress;

	if( bUseUdpIp &&
		poIdent->m_oUdpIp.isValid() )
	{
		poIdent->m_oUdpIp.toStdString( strIpAddress );
	}
	else
	{
		poIdent->m_oDirectConnectId.getIpAddress( strIpAddress );
	}
	
	RcSktConnect * poSkt = RcSktConnectMgr::ConnectTo(	strIpAddress.c_str(),		// remote ip or url 
														poIdent->getOnlinePort(),	// port to connect to
														iConnectTimeout );			// seconds before connect attempt times out
	if( poSkt )
	{
#ifdef DEBUG_SKTS
		log_msg( LOG_SKT, "RcSysSktMgr::DirectConnectTo: connect success.. setting user data\n" );
#endif // DEBUG_SKTS
		poSkt->setUserData( pvUserData );
		// generate encryption keys
#ifdef DEBUG_SKTS
		log_msg( LOG_SKT, "RcSysSktMgr::DirectConnectTo: connect success.. generating tx key\n" );
#endif // DEBUG_SKTS

		GenerateTxConnectionKey( poSkt, &poIdent->m_oDirectConnectId );
#ifdef DEBUG_SKTS
		log_msg( LOG_SKT, "RcSysSktMgr::DirectConnectTo: connect success.. generating rx key\n" );
#endif // DEBUG_SKTS

		GenerateRxConnectionKey( poSkt, &m_oPktAnn.m_oDirectConnectId );
#ifdef DEBUG_SKTS
		log_msg( LOG_SKT, "RcSysSktMgr::DirectConnectTo: connect success.. sending announce\n" );
#endif // DEBUG_SKTS

		rc = SendMyPktAnnounce( poIdent, poSkt, poIdent->getMyFriendshipToHim(), poIdent->getHisFriendshipToMe(), true );
		if( rc )
		{
#ifdef DEBUG_SKTS
			log_msg( LOG_SKT, "Error %d %s Transmitting PktAnn to contact\n", rc, poSkt->DescribeSktError( rc ) );
#endif // DEBUG_SKTS
		}

		if( ppoRetSkt )
		{
			*ppoRetSkt = (RcSktBase *)poSkt;
		}
	}
	else
	{
#ifdef DEBUG_SKTS
		log_msg( LOG_SKT, "RcSysSktMgr::DirectConnectTo: failed\n" );
#endif // DEBUG_SKTS
	}
#ifdef DEBUG_SKTS
	log_msg( LOG_SKT, "RcSysSktMgr::DirectConnectTo: done\n" );
#endif // DEBUG_SKTS

	return rc;
}

//============================================================================
//! attempt connect to remote user's proxy ( only connects and send pkt announce )
RCODE RcSysSktMgr::RmtUserRelayConnectTo(	VxNetIdentBase *		poIdent,			// network identity
											RcSktBase **			ppoRetSkt,			// return pointer to socket if not null
											int						iConnectTimeout,	// seconds before connect attempt times out
											void *					pvUserData )		// pointer to user additional user defined data
{
	RCODE rc = -1;
	std::string strIpAddress;
	poIdent->m_oRelayConnectId.getIpAddress( strIpAddress );

	RcSktConnect * poSkt = RcSktConnectMgr::ConnectTo(	strIpAddress.c_str(),			// remote ip or url 
														poIdent->m_oRelayConnectId.m_u16OnlinePort,	// port to connect to
														iConnectTimeout );				// seconds before connect attempt times out
	if( poSkt )
	{
		poSkt->setUserData( pvUserData );
		// generate encryption keys
		GenerateTxConnectionKey( poSkt, &poIdent->m_oRelayConnectId );
		GenerateRxConnectionKey( poSkt, &m_oPktAnn.m_oDirectConnectId );

		// we are connected to users proxy
		// first send announcement to his proxy then to him
		OnlineId& oRelayOnlineId = poIdent->m_oRelayConnectId.getOnlineId();
		bool bResult =  SendMyPktAnnounce(	oRelayOnlineId, 
											poSkt, 
											eFriendStateAnonymous, 
											eFriendStateAnonymous,
											true );
		if( true == bResult )
		{
			// now send announce to remote user
			bool requestReverseConnection = ( ( false == m_oPktAnn.requiresRelay() ) && poIdent->requiresRelay() );
			bool requestSTUN = ( ( m_oPktAnn.requiresRelay() ) && poIdent->requiresRelay() );

			rc = SendMyPktAnnounce( poIdent, 
									poSkt, 
									poIdent->getMyFriendshipToHim(), 
									poIdent->getHisFriendshipToMe(), 
									true, 
									requestReverseConnection,
									requestSTUN );
			if( rc )
			{
				log_msg( 0, "Error %d %s Transmitting PktAnn to contact\n", rc, poSkt->DescribeSktError( rc ) );
				poSkt->CloseSkt( 2154 );
				poSkt = NULL;
			}
		}
		else
		{
			poSkt->CloseSkt( 2155 );
			poSkt = NULL;
		}

		if( ppoRetSkt )
		{
			*ppoRetSkt = (RcSktBase *)poSkt;
		}
	}
	return rc;

}

